package com.treeroot.springbootwithelastic.com.treeroot.es.tools;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.xcontent.XContentType;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;

/**
 * @author treeroot
 * es文档工具类包
 * (elasticsearch-7.17.11.jar)
 */
@Component
@Slf4j
public class DocAction {
    @Resource
    private RestHighLevelClient restHighLevelClient;

    /**
     * 1. 创建文档信息对象
     * 2. 创建文档
     * 3. 获取响应结果
     *
     * @param obj      文档数据 [entity实体类]
     * @param docIndex 文档所在的索引
     * @return DocWriteResponse.Result [CREATED, UPDATED]
     */
    public DocWriteResponse.Result insertDocReq(Object obj, String docIndex) throws Exception {
        IndexRequest source = new IndexRequest(docIndex).id("1").source(obj, XContentType.JSON);
        return restHighLevelClient.index(source, RequestOptions.DEFAULT).getResult();
    }

    /**
     * 指定文档返回数据
     *
     * @param getRequest 查询对象请求
     */
    public GetResponse getDocReq(GetRequest getRequest) throws Exception {
        return restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
    }

    /**
     * 判断文档是否存在
     */
    public boolean existsDoc(GetRequest getRequest) throws Exception {
        return restHighLevelClient.exists(getRequest, RequestOptions.DEFAULT);
    }


    /**
     * 1.创建查询对象
     * 2.获得查询的数据
     * 3.返回数据转换回类中返回
     * 查询文档,查不到就创建
     *
     * @param obj      文档数据 [entity实体类]
     * @param docIndex 文档所在的索引
     * @return Class
     * @throws Exception 异常
     */
    public Class getDocReqMust(Object obj, String docIndex, String dcoId, Class clas) throws Exception {
        GetRequest getRequest = new GetRequest(docIndex).id(dcoId);
        if (existsDoc(getRequest)) {
            String sourceAsString = getDocReq(getRequest).getSourceAsString();
            return JSON.parseObject(sourceAsString, clas).getClass();
        } else {
            insertDocReq(obj, docIndex);
            return getDocReqMust(obj, docIndex, dcoId, clas);
        }
    }

    /**
     * 1.创建查询对象
     * 2.获得查询的数据
     * 3.返回数据转换回类中返回
     * 查询文档
     *
     * @param obj      文档数据 [entity实体类]
     * @param docIndex 文档所在的索引
     * @return Class
     * @throws Exception 异常
     */
    public Class getDocReq(Object obj, String docIndex, String dcoId, Class clas) throws Exception {
        GetRequest getRequest = new GetRequest(docIndex).id(dcoId);
        if (existsDoc(getRequest)) {
            String sourceAsString = getDocReq(getRequest).getSourceAsString();
            return JSON.parseObject(sourceAsString, clas).getClass();
        } else {
            insertDocReq(obj, docIndex);
            return getDocReq(obj, docIndex, dcoId, clas);
        }
    }

    /**
     * 创建修改信息对象，指定索引和文档id
     * 修改文档
     *
     * @param obj      文档数据 [entity实体类]
     * @param docIndex 文档所在的索引
     * @param docId    文档id
     * @return String [update]
     * @throws Exception 异常
     */
    public String updateDocReq(Object obj, String docIndex, String docId) throws Exception {
        UpdateRequest updateRequest = new UpdateRequest(docIndex, docId);
        //封装要修改的值----以POST形式修改指定列
        updateRequest.doc(JSON.toJSONString(obj), XContentType.JSON);
        UpdateResponse update = restHighLevelClient.update(updateRequest, RequestOptions.DEFAULT);
        return update.getResult().toString();
    }

    /**
     * 批量添加文档
     *
     * @param objectList 传入的list集合
     * @param docId      传入的索引
     * @return boolean
     * @throws Exception 异常
     */
    public BulkResponse batchAddDoc(List<?> objectList, String docId) throws Exception {
        BulkRequest bulkRequest = new BulkRequest(docId);
        //使用stream形式将list内的数据封装到容器中
        objectList.stream().forEach(item -> {
            bulkRequest.add(new IndexRequest().source(JSON.toJSONString(item), XContentType.JSON));
        });
        //将封装好的信息放入该方法中进行批量添加---将会自动生成id
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        //判断是否添加失败，若成功添加则返回false
        return bulk;
    }

    /**
     * 批量删除文档
     *
     * @param docIds   文档id
     * @param docIndex 文档索引
     * @return DocWriteResponse.Result
     * @throws Exception 异常
     */
    public DocWriteResponse.Result deleteDocReq(String[] docIds, String docIndex) throws Exception {
        //创建删除对象---指定索引
        boolean ifOk = Boolean.TRUE;
        DeleteRequest deleteRequest = new DeleteRequest(docIndex);
        Arrays.stream(docIds).forEach(item -> {
            //将要删除id封装到删除对象中
            deleteRequest.id(item);
            try {
                //调用删除方法
                DeleteResponse delete = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
                //获取删除返回值----成功返回为true，失败则返回false
                //todo:赋值给ifOk
                System.out.println(delete.getResult());
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        return null;
    }

    /**
     * 自带分页需要提供查询生成器的条件查询
     *
     * @param complexPagingSearchCmd 复杂搜索命令
     * @return 总数与数据
     * @throws IOException 异常
     */
    public Map<String, Object> searchReqByPage(ComplexPagingSearchCmd complexPagingSearchCmd) throws IOException {
        //创建复杂查询对象
        SearchRequest searchRequest = new SearchRequest(complexPagingSearchCmd.getDocIndex());
        //条件封装器
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //使用条件对象来封装查询条件
        searchSourceBuilder.query(complexPagingSearchCmd.getMatchQueryBuilder());
        //分页
        searchSourceBuilder.from((complexPagingSearchCmd.getCurrentPage() - 1) * complexPagingSearchCmd.getPageSize());
        searchSourceBuilder.size(complexPagingSearchCmd.getPageSize());
        //排序
        //searchSourceBuilder.sort()
        //得到结果
        searchRequest.source(searchSourceBuilder);
        SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        log.info("查询结果{}", search);
        SearchHit[] hits = search.getHits().getHits();
        List<Map<String, Object>> list = new ArrayList<>();
        long totalHits = search.getHits().getTotalHits().value;
        //总条数total
        log.info("一共{}条", totalHits);
        Arrays.stream(hits).forEach(item -> {
            Map<String, Object> sourceAsMap = item.getSourceAsMap();
            list.add(sourceAsMap);
        });
        //将查出来的数据和总条数存储在map中
        Map<String, Object> map = new HashMap<>();
        map.put("list", list);
        map.put("total", totalHits);
        return map;
    }

    /**
     * 更推荐~更灵活的查询
     * 构建searchSourceBuilder和searchRequest的查询
     *
     * @param complexPagingSearchCmd 复杂搜索命令
     * @return 总数与数据
     * @throws IOException 异常
     */
    public Map<String, Object> searchNeedSearchSource(ComplexPagingSearchCmd complexPagingSearchCmd) throws IOException {
        //创建复杂查询对象
        SearchRequest searchRequest = new SearchRequest(complexPagingSearchCmd.getDocIndex());
        //得到结果
        searchRequest.source(complexPagingSearchCmd.getSearchSourceBuilder());
        SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        log.info("查询结果{}", search);
        SearchHit[] hits = search.getHits().getHits();
        List<Map<String, Object>> list = new ArrayList<>();
        long totalHits = search.getHits().getTotalHits().value;
        //总条数total
        log.info("一共{}条", totalHits);
        Arrays.stream(hits).forEach(item -> {
            Map<String, Object> sourceAsMap = item.getSourceAsMap();
            list.add(sourceAsMap);
        });
        //将查出来的数据和总条数存储在map中
        Map<String, Object> map = new HashMap<>();
        map.put("list", list);
        map.put("total", totalHits);
        return map;
    }


    /**
     * this is a reqSearch demo
     *
     * @throws IOException 异常
     */
    public void searchReq() throws IOException {
        //创建复杂查询对象---------
        SearchRequest searchRequest = new SearchRequest("zfy-test");
        //创建一个条件对象用来封装各种条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //创建一个对象来封装查询条件query
        MatchQueryBuilder queryBuilder = new MatchQueryBuilder("name", "李");
        //使用条件对象来封装查询条件---条件
        sourceBuilder.query(queryBuilder);

        //指定查询的列------条件
        String[] arr = {"name", "address", "age"};
        String[] att = {};
        //指定查询列和不查询列
        sourceBuilder.fetchSource(arr, att);

        //分页查询---条件
        sourceBuilder.from(0);
        sourceBuilder.size(5);
        //排序----条件
        // SortOrder sortOrder = SortOrder.ASC;------定义排序方式
        sourceBuilder.sort("age", SortOrder.DESC);

        //创建一个高亮对象，封装高亮条件
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        //指定高亮列----也可以用fields，全局定义高亮
        highlightBuilder.field("name");
        highlightBuilder.preTags("<font color='red'>");
        highlightBuilder.postTags("</font>");

        sourceBuilder.highlighter(highlightBuilder);
        //将封装好的条件对象给searchRequest
        searchRequest.source(sourceBuilder);
        //将封装好所有条件的对象给查询方法
        SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        //输出根据条件查出的总条数
        System.out.println("命中条数/查询总数:" + search.getHits().getTotalHits().value);

        //将查出的数据封住为一个数组
        SearchHit[] hits = search.getHits().getHits();
        //遍历数组
        Arrays.stream(hits).forEach(item -> {
            //输出遍历对象
            System.out.println(item.getSourceAsString());
        });
        Arrays.stream(hits).forEach(item -> {
            //遍历输出高亮的字段
            System.out.println(item.getHighlightFields());
        });
    }
}
